package logger

import (
	"context"
	"fmt"
	"gitee.com/finally/logger/metrics"
	syslogUtil "gitee.com/finally/logger/utils/syslog"
	"io/ioutil"
	"os"
	"path"
	"sync"

	log "go.uber.org/zap"
	"go.uber.org/zap/zapcore"

	zapsyslog "github.com/lemonlinger/zap-syslog"
	"github.com/lemonlinger/zap-syslog/encoders/logfmt"
	"github.com/lemonlinger/zap-syslog/syslog"
)

// ZapLoggerAdapter is the adaptor based on Zap
type ZapLoggerAdapter struct {
	LoggerAdapter
	logger       *log.Logger
	atom         log.AtomicLevel
	contextHooks ContextHooks
	metrics      *metrics.LogMetrics
}

func newZapLogger(name string, c *Config) (*ZapLoggerAdapter, error) {
	if err := c.Valid(); err != nil {
		return nil, err
	}

	l := new(ZapLoggerAdapter)
	l.metrics = metrics.GetLogMetrics()
	l.SetName(name)
	l.atom = log.NewAtomicLevel()
	encCfg := zapcore.EncoderConfig{
		TimeKey:        c.getTimeKey(),
		LevelKey:       c.getLevelKey(),
		CallerKey:      c.getCallerKey(),
		MessageKey:     c.getMsgKey(),
		NameKey:        "logger",
		StacktraceKey:  "stacktrace",
		EncodeLevel:    zapcore.LowercaseLevelEncoder,
		EncodeTime:     zapcore.ISO8601TimeEncoder,
		EncodeDuration: zapcore.SecondsDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder,
	}

	var enc zapcore.Encoder
	var sink zapcore.WriteSyncer
	var err error

	switch c.Formatter {
	case JSONFormatter:
		enc = zapcore.NewJSONEncoder(encCfg)
	case TextFormatter:
		enc = zapcore.NewConsoleEncoder(encCfg)
	case LogfmtFormatter:
		enc = logfmt.NewEncoder(encCfg)
	}

	switch c.Output {
	case Discard:
		sink = zapcore.AddSync(ioutil.Discard)
	case Stdout:
		sink = zapcore.AddSync(os.Stdout)
	case Stderr:
		sink = zapcore.AddSync(os.Stderr)
	case File:
		sink, err = os.OpenFile(c.File.FileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
		if err != nil {
			return nil, err
		}
	case Syslog:
		// syslog output only supports JSON and logfmt formatter.
		enc, err = newSyslogEncoder(c.Syslog, &encCfg, c.Formatter)
		if err != nil {
			return nil, err
		}
		sink, err = zapsyslog.NewConnSyncer(c.Syslog.Network, c.Syslog.Addr)
		if err != nil {
			return nil, err
		}
	}

	callerSkip := 1
	if c.callerSkip > 1 {
		callerSkip = c.callerSkip
	}
	logger := log.New(
		zapcore.NewCore(
			enc,
			zapcore.Lock(sink),
			l.atom,
		),
		log.AddCallerSkip(callerSkip),
		log.AddCaller(),
	)

	l.logger = logger
	l.SetLevel(c.Level)
	return l, nil
}

func newSyslogEncoder(c *SyslogConfig, encCfg *zapcore.EncoderConfig, f Formatter) (zapcore.Encoder, error) {
	hostname, err := os.Hostname()
	if err != nil {
		return nil, err
	}
	if c.Tag == "" {
		c.Tag = path.Base(os.Args[0])
	}
	enc := zapsyslog.NewSyslogEncoder(zapsyslog.SyslogEncoderConfig{
		EncoderConfig: *encCfg,
		Facility:      syslog.Priority(syslogUtil.GetPriority(c.Facility)),
		Hostname:      hostname,
		PID:           os.Getpid(),
		App:           c.Tag,
		Formatter:     string(f),
	})

	return enc, nil
}

// Close logger can do some cleanup jobs such as flushing any buffered log entries.
func (l *ZapLoggerAdapter) Close() error {
	return l.logger.Sync()
}

// SetLevel set logging level
func (l *ZapLoggerAdapter) SetLevel(level Level) {
	l.LoggerAdapter.SetLevel(level)
	switch level {
	case LevelTrace:
		l.atom.SetLevel(zapcore.DebugLevel)
	case LevelDebug:
		l.atom.SetLevel(zapcore.DebugLevel)
	case LevelInfo:
		l.atom.SetLevel(zapcore.InfoLevel)
	case LevelWarn:
		l.atom.SetLevel(zapcore.WarnLevel)
	case LevelError:
		l.atom.SetLevel(zapcore.ErrorLevel)
	case LevelFatal:
		l.atom.SetLevel(zapcore.FatalLevel)
	}
}

// AddContextHook adds a context hook.
func (l *ZapLoggerAdapter) AddContextHook(h ContextHook) {
	l.contextHooks = append(l.contextHooks, h)
}

// ClearContextHooks clear all context hooks.
func (l *ZapLoggerAdapter) ClearContextHooks() {
	l.contextHooks = nil
}

// AddCallerSkip create a child logger with a incresed caller skip
func (l *ZapLoggerAdapter) AddCallerSkip(skip int) Logger {
	if skip == 0 {
		return l
	}
	return &ZapLoggerAdapter{
		LoggerAdapter: l.LoggerAdapter,
		logger:        l.logger.WithOptions(log.AddCallerSkip(skip)),
		atom:          l.atom,
		contextHooks:  append(l.contextHooks[:0:0], l.contextHooks...),
		metrics:       l.metrics,
	}
}

// WithError adds an error as a field into logger.
func (l *ZapLoggerAdapter) WithError(err error) Logger {
	if err == nil {
		return l
	}
	return &ZapLoggerAdapter{
		LoggerAdapter: l.LoggerAdapter,
		logger:        l.logger.With(log.Error(err)),
		atom:          l.atom,
		contextHooks:  append(l.contextHooks[:0:0], l.contextHooks...),
		metrics:       l.metrics,
	}
}

// WithField adds a field into the logger.
// It doesn't log unitl any of Debugln(f/w)/Infoln(f/w)/Warnln(f/w)/Errorln(f/w)/Fatalln(f/w) methods is called.
func (l *ZapLoggerAdapter) WithField(key string, val interface{}) Logger {
	if key == "" {
		return l
	}
	return &ZapLoggerAdapter{
		LoggerAdapter: l.LoggerAdapter,
		logger:        l.logger.With(log.Reflect(key, val)),
		atom:          l.atom,
		contextHooks:  append(l.contextHooks[:0:0], l.contextHooks...),
		metrics:       l.metrics,
	}
}

// WithFields adds multiple fields into the logger.
// It doesn't log unitl any of Debugln(f/w)/Infoln(f/w)/Warnln(f/w)/Errorln(f/w)/Fatalln(f/w) methods is called.
func (l *ZapLoggerAdapter) WithFields(fields ...Field) Logger {
	if len(fields) == 0 {
		return l
	}
	s := convertFields(fields...)
	return &ZapLoggerAdapter{
		LoggerAdapter: l.LoggerAdapter,
		logger:        l.logger.With(s...),
		atom:          l.atom,
		contextHooks:  append(l.contextHooks[:0:0], l.contextHooks...),
		metrics:       l.metrics,
	}
}

func convertFields(fields ...Field) []log.Field {
	s := make([]log.Field, len(fields))
	for i, f := range fields {
		s[i] = log.Field{
			Key:       f.Key,
			Type:      zapcore.FieldType(f.Type),
			Integer:   f.Integer,
			String:    f.String,
			Interface: f.Interface,
		}
	}
	return s
}

func (l *ZapLoggerAdapter) execContextHooks(ctx context.Context) []Field {
	var fields []Field
	if ctx != nil && len(l.contextHooks) != 0 {
		for _, h := range l.contextHooks {
			fields = append(fields, h(ctx)...)
		}
	}
	return fields
}

// WithContext extracts fields from context and adds that into the logger.
// Note that extracting fields are done through calling context hooks.
// It doesn't log unitl any of Debugln(f/w)/Infoln(f/w)/Warnln(f/w)/Errorln(f/w)/Fatalln(f/w) methods is called.
func (l *ZapLoggerAdapter) WithContext(ctx context.Context) Logger {
	s := convertFields(l.execContextHooks(ctx)...)
	if len(s) == 0 {
		return l
	}
	return &ZapLoggerAdapter{
		LoggerAdapter: l.LoggerAdapter,
		logger:        l.logger.With(s...),
		atom:          l.atom,
		contextHooks:  append(l.contextHooks[:0:0], l.contextHooks...),
		metrics:       l.metrics,
	}
}

// Infow logs a message at Info level. The message includes any fields passed at the log site,
// as well as any fields accumulated on the logger.
func (l *ZapLoggerAdapter) Infow(msg string, fields ...Field) {
	if l.IsEnableInfo() {
		l.metrics.LogEntry(levelInfoString)
		l.logger.Info(msg, convertFields(fields...)...)
	}
}

// Warnw logs a message at Warn level. The message includes any fields passed at the log site,
// as well as any fields accumulated on the logger.
func (l *ZapLoggerAdapter) Warnw(msg string, fields ...Field) {
	if l.IsEnableWarn() {
		l.metrics.LogEntry(levelWarnString)
		l.logger.Warn(msg, convertFields(fields...)...)
	}
}

// Errorw logs a message at Error level. The message includes any fields passed at the log site,
// as well as any fields accumulated on the logger.
func (l *ZapLoggerAdapter) Errorw(msg string, fields ...Field) {
	if l.IsEnableError() {
		l.metrics.LogEntry(levelErrorString)
		l.logger.Error(msg, convertFields(fields...)...)
	}
}

// Fatalw logs a message at Fatal level. The message includes any fields passed at the log site,
// as well as any fields accumulated on the logger.
func (l *ZapLoggerAdapter) Fatalw(msg string, fields ...Field) {
	if l.IsEnableFatal() {
		l.metrics.LogEntry(levelFatalString)
		l.logger.Fatal(msg, convertFields(fields...)...)
	}
}

// Debugw logs a message at Debug level. The message includes any fields passed at the log site,
// as well as any fields accumulated on the logger.
func (l *ZapLoggerAdapter) Debugw(msg string, fields ...Field) {
	if l.IsEnableDebug() {
		l.metrics.LogEntry(levelDebugString)
		l.logger.Debug(msg, convertFields(fields...)...)
	}
}

// Infoln logs arguments and any fields accumulated on the logger at Info level.
func (l *ZapLoggerAdapter) Infoln(v ...interface{}) {
	if l.IsEnableInfo() {
		l.metrics.LogEntry(levelInfoString)
		msg := fmt.Sprintln(v...)
		l.logger.Info(msg[:len(msg)-1])
	}
}

// Infof logs arguments and any fields accumulated on the logger at Info level.
// Arguments are handled in the manner of fmt.Printf.
func (l *ZapLoggerAdapter) Infof(format string, v ...interface{}) {
	if l.IsEnableInfo() {
		l.metrics.LogEntry(levelInfoString)
		l.logger.Info(fmt.Sprintf(format, v...))
	}
}

// Warnln logs arguments and any fields accumulated on the logger at Warn level.
func (l *ZapLoggerAdapter) Warnln(v ...interface{}) {
	if l.IsEnableWarn() {
		l.metrics.LogEntry(levelWarnString)
		msg := fmt.Sprintln(v...)
		l.logger.Warn(msg[:len(msg)-1])
	}
}

// Warnf logs arguments and any fields accumulated on the logger at Warn level.
// Arguments are handled in the manner of fmt.Printf.
func (l *ZapLoggerAdapter) Warnf(format string, v ...interface{}) {
	if l.IsEnableWarn() {
		l.metrics.LogEntry(levelWarnString)
		l.logger.Warn(fmt.Sprintf(format, v...))
	}
}

// Errorln logs arguments and any fields accumulated on the logger at Error level.
func (l *ZapLoggerAdapter) Errorln(v ...interface{}) {
	if l.IsEnableError() {
		l.metrics.LogEntry(levelErrorString)
		msg := fmt.Sprintln(v...)
		l.logger.Error(msg[:len(msg)-1])
	}
}

// Errorf logs arguments and any fields accumulated on the logger at Error level.
// Arguments are handled in the manner of fmt.Printf.
func (l *ZapLoggerAdapter) Errorf(format string, v ...interface{}) {
	if l.IsEnableError() {
		l.metrics.LogEntry(levelErrorString)
		l.logger.Error(fmt.Sprintf(format, v...))
	}
}

// Fatalln logs arguments and any fields accumulated on the logger at Fatal level.
func (l *ZapLoggerAdapter) Fatalln(v ...interface{}) {
	if l.IsEnableFatal() {
		l.metrics.LogEntry(levelFatalString)
		msg := fmt.Sprintln(v...)
		l.logger.Fatal(msg[:len(msg)-1])
	}
}

// Fatalf logs arguments and any fields accumulated on the logger at Fatal level.
// Arguments are handled in the manner of fmt.Printf.
func (l *ZapLoggerAdapter) Fatalf(format string, v ...interface{}) {
	if l.IsEnableFatal() {
		l.metrics.LogEntry(levelFatalString)
		l.logger.Fatal(fmt.Sprintf(format, v...))
	}
}

// Debugln logs arguments and any fields accumulated on the logger at Debug level.
func (l *ZapLoggerAdapter) Debugln(v ...interface{}) {
	if l.IsEnableDebug() {
		l.metrics.LogEntry(levelDebugString)
		msg := fmt.Sprintln(v...)
		l.logger.Debug(msg[:len(msg)-1])
	}
}

// Debugf logs arguments and any fields accumulated on the logger at Debug level.
// Arguments are handled in the manner of fmt.Printf.
func (l *ZapLoggerAdapter) Debugf(format string, v ...interface{}) {
	if l.IsEnableDebug() {
		l.metrics.LogEntry(levelDebugString)
		l.logger.Debug(fmt.Sprintf(format, v...))
	}
}

// Debugc logs arguments and fields extracted from context by hooks and accumulated on the logger at Debug level.
func (l *ZapLoggerAdapter) Debugc(ctx context.Context, msg string, fields ...Field) {
	if l.IsEnableDebug() {
		l.metrics.LogEntry(levelDebugString)
		l.logger.Debug(msg, convertFields(append(fields, l.execContextHooks(ctx)...)...)...)
	}
}

// Infoc logs arguments and fields extracted from context by hooks and accumulated on the logger at Info level.
func (l *ZapLoggerAdapter) Infoc(ctx context.Context, msg string, fields ...Field) {
	if l.IsEnableInfo() {
		l.metrics.LogEntry(levelInfoString)
		l.logger.Info(msg, convertFields(append(l.execContextHooks(ctx), fields...)...)...)
	}
}

// Warnc logs arguments and fields extracted from context by hooks and accumulated on the logger at Warning level.
func (l *ZapLoggerAdapter) Warnc(ctx context.Context, msg string, fields ...Field) {
	if l.IsEnableWarn() {
		l.metrics.LogEntry(levelWarnString)
		l.logger.Warn(msg, convertFields(append(l.execContextHooks(ctx), fields...)...)...)
	}
}

// Errorc logs arguments and fields extracted from context by hooks and accumulated on the logger at Error level.
func (l *ZapLoggerAdapter) Errorc(ctx context.Context, msg string, fields ...Field) {
	if l.IsEnableError() {
		l.metrics.LogEntry(levelErrorString)
		l.logger.Error(msg, convertFields(append(l.execContextHooks(ctx), fields...)...)...)
	}
}

// Fatalc logs arguments and fields extracted from context by hooks and accumulated on the logger at Fatal level.
func (l *ZapLoggerAdapter) Fatalc(ctx context.Context, msg string, fields ...Field) {
	if l.IsEnableFatal() {
		l.metrics.LogEntry(levelFatalString)
		l.logger.Fatal(msg, convertFields(append(l.execContextHooks(ctx), fields...)...)...)
	}
}

// ZapLoggerFactory is the factory of zap logger.
type ZapLoggerFactory struct {
	config  Config
	mux     *sync.RWMutex
	loggers map[string]Logger
}

// NewZapLoggerFactory creates a LoggerFactory building zap logger.
func NewZapLoggerFactory(c *Config) LoggerFactory {
	return &ZapLoggerFactory{
		config:  *c,
		mux:     new(sync.RWMutex),
		loggers: make(map[string]Logger),
	}
}

// GetLogger create a zap logger.
func (factory *ZapLoggerFactory) GetLogger(name string) (Logger, error) {
	factory.mux.RLock()
	l, ok := factory.loggers[name]
	factory.mux.RUnlock()
	if ok {
		return l, nil
	}
	l, err := newZapLogger(name, &factory.config)
	if err != nil {
		return nil, err
	}
	factory.mux.Lock()
	factory.loggers[name] = l
	factory.mux.Unlock()
	return l, nil
}

// SetLogger save a logger
func (factory *ZapLoggerFactory) SetLogger(name string, l Logger) error {
	factory.mux.Lock()
	factory.loggers[name] = l
	factory.mux.Unlock()
	return nil
}
